/*
 * Copyright 2011 Angel Sanadinov
 *
 * This file is part of VBox WMI.
 *
 * VBox WMI is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * VBox WMI is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with VBox WMI.  If not, see <http://www.gnu.org/licenses/>.
 */

package Utilities;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * This class is a utility for reading configuration files. <br><br>
 * An instance of this class is retrieved by calling <code>ConfigurationReader.getReader()</code>
 * with the location of the configuration file as a parameter.<br>
 * The file is then opened and read, with all the parameters saved in the <code>ConfigurationReader</code>
 * that is returned by the <code>getReader()</code> method.
 *
 * @see ConfigurationReader#getReader(java.lang.String)
 *
 * @author Angel Sanadinov
 */
public final class ConfigurationReader
{
    private static final String PARAM_VALUE_DELIMITER = "=";

    /** Configuration file parameter: Database driver */
    public static final String PARAM_DATABASE_DRIVER = "DATABASE_DRIVER";
    /** Configuration file parameter: Database subprotocol */
    public static final String PARAM_DATABASE_SUBPROTOCOL = "DATABASE_SUBPROTOCOL";
    /** Configuration file parameter: Database address */
    public static final String PARAM_DATABASE_ADDRESS = "DATABASE_ADDRESS";
    /** Configuration file parameter: Database port */
    public static final String PARAM_DATABASE_PORT = "DATABASE_PORT";
    /** Configuration file parameter: Database name */
    public static final String PARAM_DATABASE_NAME = "DATABASE_NAME";
    /** Configuration file parameter: Database username */
    public static final String PARAM_DATABASE_USER = "DATABASE_USER";
    /** Configuration file parameter: Database password */
    public static final String PARAM_DATABASE_PASSWORD = "DATABASE_PASSWORD";

    /** Configuration file parameter: Logs file */
    public static final String PARAM_LOGS_FILE = "LOGS_FILE";
    /** Configuration file parameter: System logging level */
    public static final String PARAM_SYSTEM_LOGGING_LEVEL = "SYSTEM_LOGGING_LEVEL";
    /** Configuration file parameter: Action reporting logging level */
    public static final String PARAM_REPORTING_LOGGING_LEVEL = "REPORTING_LOGGING_LEVEL";

    /* System Settings */
    /** Configuration file parameter: Default HTTP response compressions algorithm */
    public static final String PARAM_COMPRESSION_ALGORITHM = "COMPRESSION_ALGORITHM";
    /** Configuration file parameter: Hashing algorithm for user passwords */
    public static final String PARAM_PASSWORD_HASHING_ALGORITHM = "PASSWORD_HASHING_ALGORITHM";
    /** Configuration file parameter: Default charset used by the application */
    public static final String PARAM_DEFAULT_CHARSET = "DEFAULT_CHARSET";
    /** Configuration file parameter: Size of the permissions manager's cache */
    public static final String PARAM_PERMISSIONS_CACHE_SIZE = "PERMISSIONS_CACHE_SIZE";
    /** Configuration file parameter: Initial capacity of the sessions manager's users data structure */
    public static final String PARAM_SESSIONS_USERS_MAP_INITIAL_CAPACITY = "SESSIONS_USERS_MAP_INITIAL_CAPACITY";
    /** Configuration file parameter: Load factor for the sessions manager's users data structure */
    public static final String PARAM_SESSIONS_USERS_MAP_LOAD_FACTOR = "SESSIONS_USERS_MAP_LOAD_FACTOR";
    /** Configuration file parameter: Capacity of the VirtualBox connections manager's users list */
    public static final String PARAM_VBCONNECTIONS_USERS_LIST_CAPACITY = "VBOX_CONNECTIONS_USERS_LIST_CAPACITY";
    /** Configuration file parameter: Maximum capacity of a user's response messages container */
    public static final String PARAM_MAX_RESPONSE_MESSAGES = "MAX_RESPONSE_MESSAGES";
    /** Configuration file parameter: Maximum wait time for a VirtualBox asynchronous operation (in ms) */
    public static final String PARAM_MAX_OPERATION_WAIT_TIME = "MAX_OPERATION_WAIT_TIME";
    /** Configuration file parameter: Capacity of the logs manager's pending logs list */
    public static final String PARAM_PENDING_LOGS_LIST_CAPACITY = "PENDING_LOGS_LIST_CAPACITY";
    /** Configuration file parameter: Type of the remote VirtualBox session initiated by the system */
    public static final String PARAM_REMOTE_VBOX_SESSION_TYPE = "REMOTE_VBOX_SESSION_TYPE";
    /** Configuration file parameter: Database connection verification state */
    public static final String PARAM_ALWAYS_VERIFY_DATABASE_CONNECTION = "ALWAYS_VERIFY_DATABASE_CONNECTION";

    //data structure for holding the parameters and their values
    private HashMap<String, String> parametersMap = new HashMap<String, String>();
    //regular expression pattern for matching parameter names and their values
    private static final Pattern parameterPattern = Pattern.compile("(.+)" + PARAM_VALUE_DELIMITER + "`(.+)`");

    /**
     * Constructs a new <code>ConfigurationReader</code> based on the supplied
     * parameters map.
     *
     * @param parametersMap the map with the retrieved parameters
     */
    private ConfigurationReader(HashMap<String, String> parametersMap)
    {
        this.parametersMap = parametersMap;
    }

    /**
     * Attempts to open the configuration file at the specified path and extracts its parameters.
     * <br><br>
     * Each validly formed parameter in the file is retrieved and once the whole file
     * has been read, the list of parameters is validated to make sure that all
     * necessary information is present. An instance of <code>ConfigurationReader</code>
     * is created and returned.
     *
     * @param configurationFilePath the path to the configuration file
     * @return a <code>ConfigurationReader</code> instance with all the parameters or
     *         <code>null</code> if not all required parameters were present
     * @throws FileNotFoundException if the specified file cannot be opened for reading
     * @throws IOException if an I/O error occurs
     *
     * @see ConfigurationReader#getParameter(java.lang.String) Getting a parameter
     * @see FileReader#FileReader(java.io.File)
     */
    public static ConfigurationReader getReader(String configurationFilePath)
            throws FileNotFoundException, IOException
    {
        ConfigurationReader configuration = null;
        BufferedReader configFile = null;
        HashMap<String, String> map = new HashMap<String, String>();

        //attempts to open the specified configuration file
        configFile = new BufferedReader(new FileReader(new File(configurationFilePath)));
        //temporary variables used in the reading process
        String currentLine, currentParameter, currentValue = null;
        boolean hasNext = true;

        do
        {
            currentLine = configFile.readLine(); //reads a line from the file

            if(currentLine != null) //checks if the end of the stream has been reached
            {
                //checks if the current line is a comment
                if(!currentLine.isEmpty() && !currentLine.substring(0, 1).equalsIgnoreCase("#"))
                {
                    Matcher parameterMatcher = parameterPattern.matcher(currentLine);
                    if(parameterMatcher.find())
                    {
                        currentParameter = parameterMatcher.group(1);
                        currentValue = parameterMatcher.group(2);
                        map.put(currentParameter, currentValue);
                    }
                    else
                        ;
                }
                else
                    ;
            }
            else //nothing more to read
                hasNext = false;
        }
        while(hasNext == true);

        //creates a new ConfigurationReader instance using the parameters that were retrieved
        configuration = new ConfigurationReader(map);
        configFile.close(); //closes the input stream

        //checks if the configuration is valid
        if(!configuration.validateParametersMap())
            configuration = null;
        else
            ;
        
        return configuration;
    }

    /**
     * Retrieves the configuration settings reader for the logs manager.
     *
     * @return the configuration settings reader
     */
    public ConfigurationReader getLogsManagerConfiguration()
    {
        HashMap<String, String> configuration = new HashMap<String, String>(4);
        configuration.put(PARAM_DEFAULT_CHARSET, this.parametersMap.get(PARAM_DEFAULT_CHARSET));
        configuration.put(PARAM_LOGS_FILE, this.parametersMap.get(PARAM_LOGS_FILE));
        configuration.put(PARAM_SYSTEM_LOGGING_LEVEL, this.parametersMap.get(PARAM_SYSTEM_LOGGING_LEVEL));
        configuration.put(PARAM_PENDING_LOGS_LIST_CAPACITY,
                          this.parametersMap.get(PARAM_PENDING_LOGS_LIST_CAPACITY));

        return new ConfigurationReader(configuration);
    }

    /**
     * Retrieves the configuration settings reader for the permissions manager.
     *
     * @return the configuration settings reader
     */
    public ConfigurationReader getPermissionsManagerConfiguration()
    {
        HashMap<String, String> configuration = new HashMap<String, String>(1);
        configuration.put(PARAM_PERMISSIONS_CACHE_SIZE, this.parametersMap.get(PARAM_PERMISSIONS_CACHE_SIZE));

        return new ConfigurationReader(configuration);
    }

    /**
     * Retrieves the configuration settings reader for the sessions manager.
     *
     * @return the configuration settings reader
     */
    public ConfigurationReader getSessionsManagerConfiguration()
    {
        HashMap<String, String> configuration = new HashMap<String, String>(3);
        configuration.put(PARAM_SESSIONS_USERS_MAP_INITIAL_CAPACITY,
                          this.parametersMap.get(PARAM_SESSIONS_USERS_MAP_INITIAL_CAPACITY));
        configuration.put(PARAM_SESSIONS_USERS_MAP_LOAD_FACTOR,
                          this.parametersMap.get(PARAM_SESSIONS_USERS_MAP_LOAD_FACTOR));
        configuration.put(PARAM_MAX_RESPONSE_MESSAGES, this.parametersMap.get(PARAM_MAX_RESPONSE_MESSAGES));

        return new ConfigurationReader(configuration);
    }

    /**
     * Retrieves the configuration settings reader for the VirtualBox connections manager.
     *
     * @return the configuration settings reader
     */
    public ConfigurationReader getConnectionsManagerConfiguration()
    {
        HashMap<String, String> configuration = new HashMap<String, String>(1);
        configuration.put(PARAM_VBCONNECTIONS_USERS_LIST_CAPACITY,
                          this.parametersMap.get(PARAM_VBCONNECTIONS_USERS_LIST_CAPACITY));

        return new ConfigurationReader(configuration);
    }

    /**
     * Retrieves the configuration settings reader for the database manager.
     *
     * @return the configuration settings reader
     */
    public ConfigurationReader getDatabaseManagerConfiguration()
    {
        HashMap<String, String> configuration = new HashMap<String, String>(7);
        configuration.put(PARAM_DATABASE_DRIVER, this.parametersMap.get(PARAM_DATABASE_DRIVER));
        configuration.put(PARAM_DATABASE_SUBPROTOCOL, this.parametersMap.get(PARAM_DATABASE_SUBPROTOCOL));
        configuration.put(PARAM_DATABASE_ADDRESS, this.parametersMap.get(PARAM_DATABASE_ADDRESS));
        configuration.put(PARAM_DATABASE_PORT, this.parametersMap.get(PARAM_DATABASE_PORT));
        configuration.put(PARAM_DATABASE_NAME, this.parametersMap.get(PARAM_DATABASE_NAME));
        configuration.put(PARAM_DATABASE_USER, this.parametersMap.get(PARAM_DATABASE_USER));
        configuration.put(PARAM_DATABASE_PASSWORD, this.parametersMap.get(PARAM_DATABASE_PASSWORD));
        configuration.put(PARAM_ALWAYS_VERIFY_DATABASE_CONNECTION,
                this.parametersMap.get(PARAM_ALWAYS_VERIFY_DATABASE_CONNECTION));

        return new ConfigurationReader(configuration);
    }

    /**
     * Retrieves the parameter with the specified name.
     *
     * @param parameterName the name of the parameter to be retrieved
     * @return the parameter or <code>null</code> if a parameter with the specified
     *         name does note exist
     */
    public String getParameter(String parameterName)
    {
        return parametersMap.get(parameterName);
    }

    /**
     * Ensures that the parameters that are needed for the operation of the application
     * are present.
     *
     * @return <code>true</code> if the map is valid or <code>false</code> otherwise
     */
    private boolean validateParametersMap()
    {
        boolean result = false;

        //checks for all required parameters
        if(parametersMap.containsKey(PARAM_DATABASE_DRIVER)
           && parametersMap.containsKey(PARAM_DATABASE_SUBPROTOCOL)
           && parametersMap.containsKey(PARAM_DATABASE_ADDRESS)
           && parametersMap.containsKey(PARAM_DATABASE_PORT)
           && parametersMap.containsKey(PARAM_DATABASE_NAME)
           && parametersMap.containsKey(PARAM_DATABASE_USER)
           && parametersMap.containsKey(PARAM_DATABASE_PASSWORD)
           && parametersMap.containsKey(PARAM_LOGS_FILE)
           && parametersMap.containsKey(PARAM_SYSTEM_LOGGING_LEVEL)
           && parametersMap.containsKey(PARAM_REPORTING_LOGGING_LEVEL)
           && parametersMap.containsKey(PARAM_COMPRESSION_ALGORITHM)
           && parametersMap.containsKey(PARAM_PASSWORD_HASHING_ALGORITHM)
           && parametersMap.containsKey(PARAM_DEFAULT_CHARSET)
           && parametersMap.containsKey(PARAM_PERMISSIONS_CACHE_SIZE)
           && parametersMap.containsKey(PARAM_SESSIONS_USERS_MAP_INITIAL_CAPACITY)
           && parametersMap.containsKey(PARAM_SESSIONS_USERS_MAP_LOAD_FACTOR)
           && parametersMap.containsKey(PARAM_VBCONNECTIONS_USERS_LIST_CAPACITY)
           && parametersMap.containsKey(PARAM_MAX_RESPONSE_MESSAGES)
           && parametersMap.containsKey(PARAM_MAX_OPERATION_WAIT_TIME)
           && parametersMap.containsKey(PARAM_PENDING_LOGS_LIST_CAPACITY)
           && parametersMap.containsKey(PARAM_REMOTE_VBOX_SESSION_TYPE)
           && parametersMap.containsKey(PARAM_ALWAYS_VERIFY_DATABASE_CONNECTION))
            result = true;
        else
            ;

        return result;
    }
}
